#ifndef _CACHE_H
#define _CACHE_H

#include <cassert>
#include <climits>
#include <map>

#include "Configuration.h"

#ifdef DEBUG
std::string DecToBin(unsigned number, unsigned bits)
{

    if ( number == 0 )
    {
    	if (bits == 1) return "0";
    	else if (bits > 3) return DecToBin(0, bits-1);
    	else return "0" + DecToBin(0, bits-1);
    }

    if ( number == 1 )
    {
    	if (bits == 1) return "1";
    	else if (bits > 3) return DecToBin(1, bits-1);
    	else return "0" + DecToBin(1, bits-1);
    }


    if ( number % 2 == 0 )
        return DecToBin(number / 2, bits-1) + "0";
    else
        return DecToBin(number / 2, bits-1) + "1";
}
#endif

class Cache
{
public:
	Cache(const Configuration& config) : m_HitCounter(0), m_MissCounter(0), m_UsageCounter(0), m_Config(config)
	{
	}

	~Cache()
	{
	}

	void access(unsigned long long adress, unsigned size)
	{
		unsigned long long keyOffset = adress & ( pow2(m_Config.offsetBits) - 1 );
		assert(keyOffset <= m_Config.maxOffset);

		unsigned long long keyIndex = (adress >> m_Config.offsetBits) & ( pow2(m_Config.indexBits) - 1 );
		assert(keyIndex <= m_Config.maxIndex);

		unsigned long long keyTag	= static_cast<unsigned>(adress >> (m_Config.offsetBits + m_Config.indexBits));
		assert(keyTag <= m_Config.maxTag);
#ifdef DEBUG
 		std::cout << DecToBin(keyTag, m_Config.tagBits) << " | " << DecToBin(keyIndex, m_Config.indexBits) << " | "
            << DecToBin(keyOffset, m_Config.offsetBits) << "\t";
#endif
		checkHit(keyIndex, keyTag);

		//for multiple lines support
		int consumedBytes = m_Config.cacheLineSize - keyOffset;
#ifdef DEBUG
 		std::cout << consumedBytes << " " << size-consumedBytes << std::endl;
#endif
		if (consumedBytes < static_cast<int>(size))
			access(adress + consumedBytes, size - consumedBytes);
	}

	unsigned getHitCount() const
	{
		return m_HitCounter;
	}

	unsigned getMissCount() const
	{
		return m_MissCounter;
	}

	double getUsage() const
	{
		return m_UsageCounter / double(m_Config.cacheLines) * 100;
	}

private:

	unsigned m_HitCounter;
	unsigned m_MissCounter;
	unsigned m_UsageCounter;

	const Configuration m_Config;

	struct CacheLine
	{
		CacheLine() : valid(false), tag(0) {}
		bool valid;
		unsigned long long tag;
	};
	typedef std::map<unsigned, CacheLine> CacheLineSet;
	std::map<unsigned, CacheLineSet> m_Data;


	void checkHit(unsigned long long keyIndex, unsigned long long keyTag)
	{
		CacheLineSet *cacheLineSet = &m_Data[keyIndex];

		for (unsigned i = 0; i < m_Config.associativityLevel; ++i)
		{
			if ((*cacheLineSet)[i].valid && (*cacheLineSet)[i].tag == keyTag)
			{
				m_HitCounter++;
#ifdef DEBUG
				std::cout << "HIT" << std::endl;
#endif
				return;
			}
		}
		m_MissCounter++;
#ifdef DEBUG
		std::cout << "MISS";
#endif
		for (unsigned i = 0; i < m_Config.associativityLevel; ++i)
		{
			// new insert
			if (!(*cacheLineSet)[i].valid)
			{
				(*cacheLineSet)[i].tag = keyTag;
				(*cacheLineSet)[i].valid = true;
				m_UsageCounter++;
#ifdef DEBUG
				std::cout << std::endl;
#endif
				return;
			}
		}
#ifdef DEBUG
		std::cout << " ( evict )" << std::endl;
#endif
		(*cacheLineSet)[0].tag = keyTag;
		(*cacheLineSet)[0].valid = true;
	}
};

#endif /* _CACHE_H */
